#include <db_manager/qls_db_conn_pool.h>

#include <QSettings>
#include <QTextCodec>
#include <QMutexLocker>

#include <settings/qls_settings.h>

QMutex QLsDBConnPool::m_instanceMutex;
QWaitCondition QLsDBConnPool::m_waitConnection;
QLsDBConnPool *QLsDBConnPool::m_instance = NULL;

QLsDBConnPool::QLsDBConnPool()
{
    /* 从ini配置文件中读取配置信息 */
    QSettings settings(QLS_FILEPATH_SETTINGS, QSettings::IniFormat);
    settings.setIniCodec(QTextCodec::codecForName("utf-8"));

    /* 获取数据库通用配置 */
    settings.beginGroup(QLS_GROUP_DB);

    m_type = settings.value(QLS_KEY_DB_TYPE, "SQLite").toString();
    m_maxWaitTime = settings.value(QLS_KEY_DB_MAX_WAIT_TIME, 1000).toInt();
    m_waitInterval = settings.value(QLS_KEY_DB_WAIT_INTERVAL, 200).toInt();
    m_maxConnectionCount = settings.value(QLS_KEY_DB_MAX_CONN_COUNT, 5).toInt();

    settings.endGroup();

    /* 获取数据库信息 */
    settings.beginGroup(m_type);

    /* mysql */
    if (m_type == "MySQL") {
        m_databaseType = "QMYSQL";
        m_hostName =
            settings.value(QLS_KEY_DB_HOST_NAME, "localhost").toString();
        m_port = settings.value(QLS_KEY_DB_PORT, "3306").toUInt();
        m_databaseName =
            settings.value(QLS_KEY_DB_DB_NAME, "qlpmsp").toString();
        m_username = settings.value(QLS_KEY_DB_USERNAME, "root").toString();
        m_password = settings.value(QLS_KEY_DB_PASSWD, "admin").toString();
    }
    /* sqlite */
    else if (m_type == "SQLite") {
        m_databaseType = "QSQLITE";
        m_databaseName =
            settings.value(QLS_KEY_DB_DB_NAME, "databases/sqlite/qlpmsp.db")
                .toString();
        /* 路径校验 - 数据库的数据文件必须存在路径，才能创建数据库文件 */
        do {
            /* 获取最后一个文件分隔符的位置 */
            int lastIndex = -1;
            if ((lastIndex = m_databaseName.lastIndexOf("/")) == -1 &&
                (lastIndex = m_databaseName.lastIndexOf("\\")) == -1)
                break;
            /* 提取数据文件的路径 */
            QString path(m_databaseName.left(lastIndex));
            /* 校验路径是否存在，如果不存在则创建 */
            QDir dir(QString("%1/%2").arg(QLsSettings::getWorkSpace(), path));
            if (!dir.exists())
                dir.mkpath(path);
        } while (0);
    }

    settings.endGroup();
}

/* 析构函数 */
QLsDBConnPool::~QLsDBConnPool()
{
    /* 销毁连接池的时候删除所有的连接 */
    foreach (QString connectionName, m_usedConnectionNames)
        QSqlDatabase::removeDatabase(connectionName);

    foreach (QString connectionName, m_unusedConnectionNames)
        QSqlDatabase::removeDatabase(connectionName);
}

/* 获取数据库连接 */
QSqlDatabase QLsDBConnPool::getConnection()
{
    QLsDBConnPool &pool = QLsDBConnPool::getInstance();
    QString connectionName;

    QMutexLocker locker(&m_instanceMutex);

    /* 已创建连接数 */
    int connectionCount =
        pool.m_unusedConnectionNames.size() + pool.m_usedConnectionNames.size();

    /* 如果连接已经用完，等待 waitInterval 毫秒看看是否有可用连接，最长等待
     * maxWaitTime 毫秒 */
    for (int i = 0;
         i < pool.m_maxWaitTime && pool.m_unusedConnectionNames.size() == 0 &&
         connectionCount == pool.m_maxConnectionCount;
         i += pool.m_waitInterval) {
        m_waitConnection.wait(&m_instanceMutex, pool.m_waitInterval);

        /* 重新计算已创建连接数 */
        connectionCount = pool.m_unusedConnectionNames.size() +
                          pool.m_usedConnectionNames.size();
    }

    if (pool.m_unusedConnectionNames.size() >
        0) { /* 有已经回收的连接，复用它们 */
        connectionName = pool.m_unusedConnectionNames.front();
        pool.m_unusedConnectionNames.pop_front();
    } else if (
        connectionCount <
        pool.m_maxConnectionCount) { /* 没有已经回收的连接，但是没有达到最大连接数，则创建新的连接
                                      */
        connectionName = QString("Connection-%1").arg(connectionCount + 1);
    } else { /* 已经达到最大连接数 */
        qDebug() << "Cannot create more connections.";
        return QSqlDatabase();
    }

    /* 创建连接 */
    QSqlDatabase db = pool.createConnection(connectionName);

    /* 有效的连接才放入 usedConnectionNames */
    if (db.isOpen())
        pool.m_usedConnectionNames.push_back(connectionName);

    return db;
}

/* 释放数据库连接回连接池 */
void QLsDBConnPool::releaseConnection(QSqlDatabase connection)
{
    QLsDBConnPool &pool = QLsDBConnPool::getInstance();
    QString connectionName = connection.connectionName();

    /* 如果是我们创建的连接，从 used 里删除，放入 unused 里 */
    if (pool.m_usedConnectionNames.contains(connectionName)) {
        QMutexLocker locker(&m_instanceMutex);
        pool.m_usedConnectionNames.removeOne(connectionName);
        pool.m_unusedConnectionNames.push_back(connectionName);
        m_waitConnection.wakeOne();
    }
}

/* 关闭所有的数据库连接 */
void QLsDBConnPool::destoryConnectionPool()
{
    QMutexLocker locker(&m_instanceMutex);
    delete m_instance;
    m_instance = NULL;
}

/* select语句 */
QSqlRecord *QLsDBConnPool::runOne(const QString &content)
{
    /* 输出sql语句 */
#ifdef QLS_DB_DEBUG
    qDebug() << "sql:" << content;
#endif

    /* 打开数据库 */
    QSqlDatabase db = QLsDBConnPool::getConnection();

    /* 执行sql语句 */
    QSqlQuery query(db);
    query.exec(content);
    if (query.isValid()) {
        qDebug() << "Invalid record: " << query.lastError();
        QLsDBConnPool::releaseConnection(db);
        exit(EOF);
    }

    /* 获取查询结果 */
    QSqlRecord *rec = nullptr;
    if (!query.first()) {
        qDebug() << "no result!";
        QLsDBConnPool::releaseConnection(db);
        return nullptr;
    } else {
        rec = new QSqlRecord(query.record());
        QLsDBConnPool::releaseConnection(db);
        return rec;
    }
}

/* insert into语句和update语句 */
void QLsDBConnPool::runNo(const QString &content)
{
    /* 输出sql语句 */
#ifdef QLS_DB_DEBUG
    qDebug() << "sql:" << content;
#endif

    /* 打开数据库 */
    QSqlDatabase db = QLsDBConnPool::getConnection();

    /* 执行sql语句 */
    QSqlQuery query(db);
    query.exec(content);
    if (query.isValid()) {
        qDebug() << "Invalid record: " << query.lastError();
        QLsDBConnPool::releaseConnection(db);
        exit(EOF);
    }

    QLsDBConnPool::releaseConnection(db);
}

// select
QList<QSqlRecord> QLsDBConnPool::runAll(const QString &content)
{
    /* 输出sql语句 */
#ifdef QLS_DB_DEBUG
    qDebug() << "sql:" << content;
#endif

    /* 打开数据库 */
    QSqlDatabase db = QLsDBConnPool::getConnection();

    /* 执行sql语句 */
    QSqlQuery query(db);
    query.exec(content);
    if (query.isValid()) {
        qDebug() << "Invalid record: " << query.lastError();
        QLsDBConnPool::releaseConnection(db);
        exit(EOF);
    }

    /* 获取查询结果 */
    QList<QSqlRecord> res;
    while (query.next()) {
        QSqlRecord rec(query.record());
        res.push_back(rec);
    }

    QLsDBConnPool::releaseConnection(db);
    return res;
}

/* 单例模式：获取数据库连接池对象 */
QLsDBConnPool &QLsDBConnPool::getInstance()
{
    /*
     * 这里判断NULL用了互斥锁，还可以直接：
     * static ConnectionPooL g_instance;
     * return g_instance;
     * 具体是否有什么区别，暂时不知道
     */
    if (NULL == m_instance) {
        QMutexLocker locker(&m_instanceMutex);

        if (NULL == m_instance)
            m_instance = new QLsDBConnPool();
    }

    return *m_instance;
}

/* 创建数据库连接 */
QSqlDatabase QLsDBConnPool::createConnection(const QString &connectionName)
{
    /* 连接已经创建过了，复用它，而不是重新创建 */
    if (QSqlDatabase::contains(connectionName)) {
        /* 连接前访问的数据库 */
        QSqlDatabase db1 = QSqlDatabase::database(connectionName);

        /* 取出检测 */
#ifdef QLS_DB_DEBUG
        qDebug() << "Test connection on borrow, execute:"
                 << "select 1;"
                 << ", for" << connectionName;
#endif

        QSqlQuery query("select 1;", db1);

        /* 如果连接断开，重新建立连接 */
        if (query.lastError().type() != QSqlError::NoError && !db1.open()) {
            qDebug() << "Open datatabase error:" << db1.lastError().text();
            return QSqlDatabase();
        }

        return db1;
    }

    /* 创建一个新的连接 */
    QSqlDatabase db = QSqlDatabase::addDatabase(m_databaseType, connectionName);
    if (m_type == "MySQL") {
        db.setHostName(m_hostName);
        db.setPort(m_port);
        db.setDatabaseName(m_databaseName);
        db.setUserName(m_username);
        db.setPassword(m_password);
    } else if (m_type == "SQLite") {
        db.setDatabaseName(m_databaseName);
    } else {
        qDebug() << "datatabase type error:" << m_type;
        return QSqlDatabase();
    }

    if (!db.open()) {
        qDebug() << "Open datatabase error:" << db.lastError().text();
        return QSqlDatabase();
    }

    return db;
}

QString QLsDBConnPool::getType()
{
    QLsDBConnPool &pool = QLsDBConnPool::getInstance();
    return pool.m_type;
}
